﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting;
using System.Text;
using System.Xml.Linq;
using com.cruxomatic.configuration;
using com.cruxomatic.logging;
using com.cruxomatic.logging.Enums;
using com.cruxomatic.web.wrokflows.DTOs;
using com.eforcegloabal.crux.web.wrokflows;

namespace com.cruxomatic.web.wrokflows
{
    public class WorkflowManager
    {
        BusinessObjectsDataContext bc = new BusinessObjectsDataContext(
            CruxConfigurationManager.GetCruxDBConnectionString());

        public int CreateWorkflowInstance(string WorkflowName, string Comments, string ActionName, string UserId, object BusinessObjectData)
        {
            try
            {
                Workflow workflow = ConfigParser.GetWorkflowConfiguration(WorkflowName);
                WorkflowInstance workflowInstance = new WorkflowInstance();
                workflowInstance.WorkflowName = workflow.WorkflowName;
                workflowInstance.WorkflowType = workflow.WorkflowType;
                workflowInstance.ActivityName = workflow.StartActivity.ActivityName;
                workflowInstance.ActionName = ActionName;
                workflowInstance.StateName = workflow.StartActivity.State;
                workflowInstance.LastModifiedBy = UserId;
                workflowInstance.LastModifiedOn = DateTime.Now;
                workflowInstance.Comments = Comments;
                Action action = workflow.StartActivity.DefaultAction;
                if (action != null && !string.IsNullOrEmpty(action.ActionDll) && !string.IsNullOrEmpty(action.ActionClass))
                    FireActionOnWorkflowInstance(workflowInstance, action.ActionName, BusinessObjectData, UserId);
                bc.WorkflowInstances.InsertOnSubmit(workflowInstance);
                bc.SubmitChanges();
                return workflowInstance.WorkflowInstanceId;
            }
            catch (Exception ex)
            {
                ApplicationLogger
                    .LogApplicationMessage(ex.Message, ApplicationLogType.Workflow, ex.StackTrace);
                throw;
            }
        }

        
        public int CreateWorkflowInstance(string WorkflowName, string ActivityName, string ActionName, string StateName, string Comments, string UserId, object BusinessObjectData)
        {
            try
            {
                Workflow workflow = ConfigParser.GetWorkflowConfiguration(WorkflowName);
                WorkflowInstance workflowInstance = new WorkflowInstance
                {
                    WorkflowName = workflow.WorkflowName,
                    WorkflowType = workflow.WorkflowType,
                    ActivityName = ActivityName,
                    ActionName = ActionName,
                    StateName = StateName,
                    LastModifiedBy = UserId,
                    LastModifiedOn = DateTime.Now,
                    Comments = Comments
                };
                bc.WorkflowInstances.InsertOnSubmit(workflowInstance);
                bc.SubmitChanges();
                return workflowInstance.WorkflowInstanceId;
            }
            catch (Exception ex)
            {
                ApplicationLogger
                    .LogApplicationMessage(ex.Message, ApplicationLogType.Workflow, ex.StackTrace);
                throw;
            }
        }


        private void FireActionOnWorkflowInstance(WorkflowInstance workFlowInstance, string ActionName, Object BusinessData, string UserId)
        {
            Action action = ConfigParser.GetAction(workFlowInstance.WorkflowName, workFlowInstance.ActivityName, ActionName);
            if (action == null)
            {
                throw new Exception("Action not found for the " + workFlowInstance.ActivityName + " activity");
            }
            //Fetch conditions on Action from config.
            var actionConditions = ConfigParser.GetConditions(workFlowInstance.WorkflowName, workFlowInstance.ActivityName, ActionName);

            ConditionResult conditionResult = new ConditionResult();
            if (actionConditions != null && actionConditions.Count > 0)
                conditionResult = GetResultantActivityByCondition(workFlowInstance, actionConditions, action, BusinessData);

            if (conditionResult.FireParentActivityPlugin)
                InvokePluggableAction(action, BusinessData, workFlowInstance);

            if (!string.IsNullOrEmpty(conditionResult.ResultantActivity))
                action.ResultantAcitivity = conditionResult.ResultantActivity;

            Activity resultantActivity = ConfigParser.GetActivity(workFlowInstance.WorkflowName, action.ResultantAcitivity);
            workFlowInstance.ActivityName = resultantActivity.ActivityName;
            workFlowInstance.ActionName = ActionName;
            workFlowInstance.StateName = resultantActivity.State;
            workFlowInstance.LastModifiedBy = UserId;
            workFlowInstance.LastModifiedOn = DateTime.Now;
            bc.SubmitChanges();

            bool emailnotifications = ConfigParser.GetWorkflowConfiguration(workFlowInstance.WorkflowName).WorkflowNotificationsOn;
            if (emailnotifications)
            {
                WorkflowEmailNotifications notifications = new WorkflowEmailNotifications();
                notifications.AppendWorkflowNotifications(workFlowInstance.WorkflowName, workFlowInstance.StateName, workFlowInstance.WorkflowInstanceId);
            }
        }

        public void FireActionOnWorkflowInstance(int workFlowInstanceId, string ActionName, string Comments, string UserId, Object BusinessData, bool AddActionToAuditTrail)
        {
            WorkflowInstance workflowInsatance = GetDbWorkflowInstance(workFlowInstanceId);
            if (AddActionToAuditTrail)
                AddToAuditTrail(workflowInsatance);
            workflowInsatance.Comments = Comments;
            FireActionOnWorkflowInstance(workflowInsatance, ActionName, BusinessData, UserId);
        }
        

        public void FireActionOnWorkflowInstance(int workFlowInstanceId, string ActionName, string UserId, Object BusinessData, bool AddActionToAuditTrail)
        {
            WorkflowInstance workflowInsatance = GetDbWorkflowInstance(workFlowInstanceId);
            if (AddActionToAuditTrail)
                AddToAuditTrail(workflowInsatance);
            FireActionOnWorkflowInstance(workflowInsatance, ActionName, BusinessData, UserId);
        }

        public void TransmitWorkflowToState(int WorkflowInstanceId, string ActivityName, string ActionName, string StateName)
        {
            WorkflowInstance workflowinstance = bc.WorkflowInstances.Single(p => p.WorkflowInstanceId == WorkflowInstanceId);
            workflowinstance.ActivityName = ActivityName;
            workflowinstance.ActionName = ActionName;
            workflowinstance.StateName = StateName;
            bc.SubmitChanges();
        }

        private void AddToAuditTrail(WorkflowInstance workflowInstance)
        {
            WorkflowInstanceAuditTrail workflowInstanceAuditTrail = new WorkflowInstanceAuditTrail();
            workflowInstanceAuditTrail.WorkflowInstanceId = workflowInstance.WorkflowInstanceId;
            workflowInstanceAuditTrail.WorkflowName = workflowInstance.WorkflowName;
            workflowInstanceAuditTrail.WorkflowType = workflowInstance.WorkflowType;
            workflowInstanceAuditTrail.ActivityName = workflowInstance.ActivityName;
            workflowInstanceAuditTrail.ActionName = workflowInstance.ActionName;
            workflowInstanceAuditTrail.Comments = workflowInstance.Comments;
            workflowInstanceAuditTrail.LastModifiedBy = workflowInstance.LastModifiedBy;
            workflowInstanceAuditTrail.LastModifiedOn = workflowInstance.LastModifiedOn;
            workflowInstanceAuditTrail.StateName = workflowInstance.StateName;
            workflowInstanceAuditTrail.WorkflowName = workflowInstance.WorkflowName;
            bc.WorkflowInstanceAuditTrails.InsertOnSubmit(workflowInstanceAuditTrail);
            bc.SubmitChanges();
        }

        private void InvokePluggableAction(Action action, Object BusinessData, WorkflowInstance workflowInstance)
        {
            if (!string.IsNullOrEmpty(action.ActionDll) && !string.IsNullOrEmpty(action.ActionClass))
            {
                ObjectHandle objectHandle = Activator.CreateInstance(action.ActionDll, action.ActionClass);
                if (objectHandle != null)
                {
                    ICruxActivity activity = (ICruxActivity)objectHandle.Unwrap();
                    activity.Action(BusinessData, action.ActionName, workflowInstance.WorkflowInstanceId);
                }
            }
        }

        private WorkflowInstance GetDbWorkflowInstance(int WorkflowInstanceId)
        {
            if (WorkflowInstanceId == 0) return new WorkflowInstance();
            return bc.WorkflowInstances.Single(p => p.WorkflowInstanceId == WorkflowInstanceId);
        }

        public IEnumerable<WorkflowInstanceInformation> GetWorkflowInstancesByState(string StateName)
        {
            IEnumerable<WorkflowInstanceInformation> workflowInstances = from allinstances in bc.WorkflowInstances
                                                                         where allinstances.StateName == StateName
                                                                         select WorkflowClassConverter.CopyWorkflowInstanceToWorkflowInstanceInformation(allinstances);
            return workflowInstances;
        }

        public IEnumerable<WorkflowInstanceInformation> GetWorkflowInstancesByType(string WorkflowType)
        {
            IEnumerable<WorkflowInstanceInformation> workflowInstances = from allinstances in bc.WorkflowInstances
                                                                         where allinstances.WorkflowType == WorkflowType
                                                                         select WorkflowClassConverter.CopyWorkflowInstanceToWorkflowInstanceInformation(allinstances);
            return workflowInstances;
        }

        public WorkflowInstanceInformation GetWorkflowInstance(int WorkflowInstanceId)
        {
            return WorkflowClassConverter.CopyWorkflowInstanceToWorkflowInstanceInformation(
            GetDbWorkflowInstance(WorkflowInstanceId));
        }
        public IEnumerable<WorkflowInstanceInformation> GetWorkflowInstances(List<int> workflowInstanceIds)
        {
            IEnumerable<WorkflowInstanceInformation> workflowInstances =
                from allinstances in bc.WorkflowInstances
                where workflowInstanceIds.Contains(allinstances.WorkflowInstanceId)
                select WorkflowClassConverter.CopyWorkflowInstanceToWorkflowInstanceInformation(allinstances);
            return workflowInstances;
        }
        public IEnumerable<WorkflowInstanceInformation> GetWorkflowInstancesByUserName(string userName)
        {
            IEnumerable<WorkflowInstanceInformation> workflowInstances =
                from wkfInstance in bc.WorkflowInstances
                where wkfInstance.LastModifiedBy.Equals(userName)
                select WorkflowClassConverter.CopyWorkflowInstanceToWorkflowInstanceInformation(wkfInstance);
            return workflowInstances;
        }
        public IEnumerable<WorkflowInstanceInformation> GetWorkflowInstanceAuditTrails(int workflowInstanceId)
        {
            if (workflowInstanceId == 0) return new List<WorkflowInstanceInformation>();
            IEnumerable<WorkflowInstanceInformation> workflowInstanceAuditTrails =
                from allinstances in bc.WorkflowInstanceAuditTrails
                where allinstances.WorkflowInstanceId == workflowInstanceId
                select WorkflowClassConverter.CopyWorkflowInstanceAuditTrailToWorkflowInstanceInformation(allinstances);
            return workflowInstanceAuditTrails;
        }

        public List<string> GetActionNamesForTheWorkflowInstance(int workflowInstanceId)
        {
            var workflowInstance = GetWorkflowInstance(workflowInstanceId);
            var actions = ConfigParser.GetActionsForActivity(workflowInstance.WorkflowName, workflowInstance.ActivityName);
            return actions.Select(a => a.ActionName).ToList();
        }

        public void DeleteWorkflow(int workflowInstanceid)
        {
            WorkflowInstance workflowInstance = GetDbWorkflowInstance(workflowInstanceid);
            bc.WorkflowInstances.DeleteOnSubmit(workflowInstance);
            bc.SubmitChanges();
        }

        public void DeleteWorkflowInstanceAuditTrail(int workflowInstanceid)
        {
            IEnumerable<WorkflowInstanceAuditTrail> audititems = from allaudits in bc.WorkflowInstanceAuditTrails
                                                                 where allaudits.WorkflowInstanceId == workflowInstanceid
                                                                 select allaudits;
            bc.WorkflowInstanceAuditTrails.DeleteAllOnSubmit(audititems);
            bc.SubmitChanges();
        }

        public bool RevertWorkflow(int workflowInstanceId)
        {
            bool success = true;
            try
            {
                IEnumerable<WorkflowInstanceInformation> workflowInstanceAuditTrails =
                    from allinstances in bc.WorkflowInstanceAuditTrails
                    where allinstances.WorkflowInstanceId == workflowInstanceId
                    orderby allinstances.LastModifiedOn ascending
                    select WorkflowClassConverter.CopyWorkflowInstanceAuditTrailToWorkflowInstanceInformation(allinstances);

                var workflowAuditTrail = workflowInstanceAuditTrails.LastOrDefault();
                if (workflowAuditTrail == null) return false;

                WorkflowInstance workflowinstance = bc.WorkflowInstances.Single(p => p.WorkflowInstanceId == workflowInstanceId);
                workflowinstance.ActivityName = workflowAuditTrail.ActivityName;
                workflowinstance.ActionName = workflowAuditTrail.ActionName;
                workflowinstance.StateName = workflowAuditTrail.StateName;
                workflowinstance.LastModifiedBy = workflowAuditTrail.LastModifiedBy;
                workflowinstance.LastModifiedOn = workflowAuditTrail.LastModifiedOn;
                workflowinstance.Attributes = workflowAuditTrail.Attribute;
                workflowinstance.WorkflowName = workflowAuditTrail.WorkflowName;
                workflowinstance.WorkflowType = workflowAuditTrail.WorkflowType;
                workflowinstance.Comments = workflowAuditTrail.Comments;

                var wkAuditTrailEntity = bc.WorkflowInstanceAuditTrails.Single(p => p.WorkflowInstanceId == workflowInstanceId);
                bc.WorkflowInstanceAuditTrails.DeleteOnSubmit(wkAuditTrailEntity);

                bc.SubmitChanges();
            }
            catch
            {
                success = false;
                
            }
            return success;
        }

        public void SetAttribute(int workflowInstanceid, List<DbAttribute> WorkflowAttributes)
        {
            StringBuilder attributestring = new StringBuilder();
            attributestring.Append("<DBAttributes>");
            foreach (var workflowAttribute in WorkflowAttributes)
            {
                attributestring.AppendFormat("<DBAttribute Name='{0}' Value='{1}'/>", workflowAttribute.Name, workflowAttribute.Value);
            }
            attributestring.Append("</DBAttributes>");
            WorkflowInstance workflowInstance = GetDbWorkflowInstance(workflowInstanceid);
            workflowInstance.Attributes = attributestring.ToString();
            bc.SubmitChanges();
        }

        public List<DbAttribute> GetAttributes(int workflowInstanceid)
        {
            WorkflowInstance workflowInstance = GetDbWorkflowInstance(workflowInstanceid);
            return ConvertAttributeXmlToDTO(workflowInstance.Attributes);
        }

        private List<DbAttribute> ConvertAttributeXmlToDTO(string attributexml)
        {
            XElement xmlAttributes = XElement.Parse(attributexml);
            return xmlAttributes.Elements("DBAttribute")
               .Select(d => new DbAttribute
               {
                   Name = (d.Attribute("Name") == null ? string.Empty : (string.IsNullOrEmpty(d.Attribute("Name").Value) ? string.Empty : d.Attribute("Name").Value)),
                   Value = (d.Attribute("Value") == null ? string.Empty : (string.IsNullOrEmpty(d.Attribute("Value").Value) ? string.Empty : d.Attribute("Value").Value)),
               }).ToList();
        }

        private ConditionResult GetResultantActivityByCondition(WorkflowInstance workFlowInstance, List<Condition> actionConditions, Action action, Object BusinessData)
        {
            ConditionResult conditionresult = new ConditionResult();
            conditionresult.FireParentActivityPlugin = false;

            bool isMeetCondition = false;
            var dbAttributes = ConvertAttributeXmlToDTO(workFlowInstance.Attributes);

            foreach (var actionCondition in actionConditions)
            {
                isMeetCondition = false;
                foreach (var subCondition in actionCondition.SubConditions)
                {
                    SubCondition condition = subCondition;
                    var dbAttribute = dbAttributes.Where(c => c.Name.ToLower().Equals(condition.AttributeName.ToLower())).FirstOrDefault();
                    if (dbAttribute != null)
                    {
                        switch (subCondition.DataType.ToLower())
                        {
                            case "int":
                            case "long":
                            case "double":
                            case "decimal":
                                if (MeetCondition(subCondition.ConditionKey, dbAttribute.Value, subCondition.Value1, subCondition.Value2, "numeric"))
                                    isMeetCondition = true;
                                else
                                    isMeetCondition = false;
                                break;
                            case "date":
                            case "datetime":
                                if (MeetCondition(subCondition.ConditionKey, dbAttribute.Value, subCondition.Value1, subCondition.Value2, "datetime"))
                                    isMeetCondition = true;
                                else
                                    isMeetCondition = false;
                                break;
                            case "text":
                            case "string":
                                if (MeetCondition(subCondition.ConditionKey, dbAttribute.Value, subCondition.Value1, subCondition.Value2, "string"))
                                    isMeetCondition = true;
                                else
                                    isMeetCondition = false;
                                break;
                            case "bool":
                            case "boolean":
                                if (MeetCondition(subCondition.ConditionKey, dbAttribute.Value, subCondition.Value1, subCondition.Value2, "boolean"))
                                    isMeetCondition = true;
                                else
                                    isMeetCondition = false;
                                break;
                        }
                    }
                    if (!isMeetCondition)
                        break;
                }
                if (isMeetCondition)
                {
                    //action.ResultantAcitivity = actionCondition.ResultantActivity;
                    conditionresult.ResultantActivity = actionCondition.ResultantActivity;

                    if (!string.IsNullOrEmpty(actionCondition.ActionClass))
                        InvokePluggableActionOnCondition(action.ActionName, actionCondition.ActionClass, BusinessData, workFlowInstance);

                    break; // no need to chk. next condition/s
                }
            }

            conditionresult.FireParentActivityPlugin = !isMeetCondition;
            return conditionresult;
        }


        private bool MeetCondition(string conditionKey, string dbAttributeValue, string actionConditionValue1, string actionConditionValue2, string dataType)
        {
            bool flag = false;
            switch (dataType.ToLower())
            {
                case "numeric":
                    var dbValue = Convert.ToDouble(dbAttributeValue);
                    var configValue1 = string.IsNullOrEmpty(actionConditionValue1) ? 0 : Convert.ToDouble(actionConditionValue1);
                    var configValue2 = string.IsNullOrEmpty(actionConditionValue2) ? 0 : Convert.ToDouble(actionConditionValue2);
                    flag = CompareNumericValues(conditionKey, dbValue, configValue1, configValue2);
                    break;
                case "datetime":
                    var dbValueDt = Convert.ToDateTime(dbAttributeValue);
                    var configValue1Dt = string.IsNullOrEmpty(actionConditionValue1) ? DateTime.Now : Convert.ToDateTime(actionConditionValue1);
                    var configValue2Dt = string.IsNullOrEmpty(actionConditionValue2) ? DateTime.Now : Convert.ToDateTime(actionConditionValue2);
                    flag = CompareDateTimeValues(conditionKey, dbValueDt, configValue1Dt, configValue2Dt);
                    break;
                case "string":
                    var dbValueStr = dbAttributeValue;
                    var configValueStr = actionConditionValue1;
                    flag = CompareStringValues(conditionKey, dbValueStr, configValueStr);
                    break;
                case "boolean":
                    var dbValuebool = Convert.ToBoolean(dbAttributeValue);
                    var configValuebool = Convert.ToBoolean(actionConditionValue1);
                    flag = (dbValuebool == configValuebool);
                    break;
            }
            return flag;
        }

        private bool CompareNumericValues(string conditionKey, double dbValue, double configValue1, double configValue2)
        {
            bool flag = false;
            switch (conditionKey.ToLower())
            {
                case "equal":
                    flag = (dbValue == configValue1);
                    break;
                case "notequal":
                    flag = (dbValue != configValue1);
                    break;
                case "lessthan":
                    flag = (dbValue < configValue1);
                    break;
                case "lessthanequal":
                    flag = (dbValue <= configValue1);
                    break;
                case "greaterthan":
                    flag = (dbValue > configValue1);
                    break;
                case "greaterthanequal":
                    flag = (dbValue >= configValue1);
                    break;
                case "between":
                    flag = (dbValue >= configValue1 && dbValue <= configValue2);
                    break;
            }
            return flag;
        }

        private bool CompareDateTimeValues(string conditionKey, DateTime dbValue, DateTime configValue1, DateTime configValue2)
        {
            bool flag = false;
            switch (conditionKey.ToLower())
            {
                case "equal":
                    flag = dbValue == configValue1;
                    break;
                case "notequal":
                    flag = dbValue != configValue1;
                    break;
                case "lessthan":
                    flag = dbValue < configValue1;
                    break;
                case "lessthanequal":
                    flag = dbValue <= configValue1;
                    break;
                case "greaterthan":
                    flag = dbValue > configValue1;
                    break;
                case "greaterthanequal":
                    flag = dbValue >= configValue1;
                    break;
                case "between":
                    flag = (dbValue >= configValue1 && dbValue <= configValue2);
                    break;
            }
            return flag;
        }
        private bool CompareStringValues(string conditionKey, string dbValue, string configValue1)
        {
            bool flag = false;
            switch (conditionKey.ToLower())
            {
                case "equal":
                    flag = dbValue.Equals(configValue1);
                    break;
                case "notequal":
                    flag = !dbValue.Equals(configValue1);
                    break;
                case "in":
                case "contains":
                    flag = dbValue.Contains(configValue1);
                    break;
            }
            return flag;
        }
        private void InvokePluggableActionOnCondition(string actionName, string actionClass, Object BusinessData, WorkflowInstance workflowInstance)
        {
            if (!string.IsNullOrEmpty(actionClass))
            {
                Type dbType = Type.GetType(actionClass);
                ICruxActivity activity = null;
                if (dbType != null)
                {
                    activity = Activator.CreateInstance(dbType) as ICruxActivity;
                    if (activity != null)
                        activity.Action(BusinessData, actionName, workflowInstance.WorkflowInstanceId);
                }
            }
        }
    }
}
